//
//  HLDownLoader.m
//  HLDownLoader
//
//  Created by hulian-ios on 17/9/16.
//  Copyright © 2017年 hulian-LB. All rights reserved.
//

#import "HLDownLoader.h"
#import "HLFileTool.h"
#import "AppDelegate.h"

#define kCachePath NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject
#define kTmpPath NSTemporaryDirectory()

@interface HLDownLoader () <NSURLSessionDataDelegate>
{
    // 记录文件临时下载大小
    long long _tmpSize;
    // 记录文件总大小
    long long _totalSize;
}
/** 下载会话 */
@property (nonatomic, strong) NSURLSession *session;
/** 下载完成路径 */
@property (nonatomic, copy) NSString *downLoadedPath;
/** 下载临时路径 */
@property (nonatomic, copy) NSString *downLoadingPath;
/** 文件输出流 */
@property (nonatomic, strong) NSOutputStream *outputStream;
/** 当前下载任务 */
@property (nonatomic, weak) NSURLSessionDataTask *dataTask;

@end

@implementation HLDownLoader


#pragma mark - 懒加载
/**
 懒加载会话
 @return 会话
 */
- (NSURLSession *)session
{
    if (!_session) {
//        NSString *identifier = @"HuLianTec.HLAerospace.BackgroundSession";
        NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
        _session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                delegate:self
                                           delegateQueue:[NSOperationQueue mainQueue]];
    }
    return _session;
}

#pragma mark - 提供给外界的接口

- (void)hl_downLoader:(NSURL *)url downLoadInfo:(DownLoadInfoType)downLoadInfo progress:(ProgressBlockType)progressBlock success:(SuccessBlockType)successBlock failed:(FailedBlockType)failedBlock
{
    self.downLoadInfo = downLoadInfo;
    self.progressChange = progressBlock;
    self.successBlock = successBlock;
    self.faildBlock = failedBlock;
    
    [self downLoader:url];
}


/**
 根据URL地址下载资源, 如果任务已经存在, 则执行继续动作
 @param url 资源路径
 */
- (void)downLoader:(NSURL *)url
{
    if ([url isEqual:self.dataTask.originalRequest.URL]) {
        // 下载失败 判断当前的状态, 如果是暂停状态
        if (self.state == HLDownLoadStatePause) {
            // 继续
            [self resumeCurrentTask];
            return;
        }
    }
    
    [self cacelCurrentTask];
    
    // 获取文件名称, 指明路径, 开启一个新任务
    NSString *fileName = url.lastPathComponent;
    self.downLoadedPath = [kCachePath stringByAppendingPathComponent:fileName];
    self.downLoadingPath = [kTmpPath stringByAppendingPathComponent:fileName];
    
    if ([HLFileTool fileExists:self.downLoadedPath]) {
        self.state = HLDownLoadStatePauseSuccess;
        return;
    }

    if (![HLFileTool fileExists:self.downLoadingPath]) {
        // 从0字节开始请求资源
        [self downLoadWithURL:url offset:0];
        return;
    }
    
    _tmpSize = [HLFileTool fileSize:self.downLoadingPath];
    [self downLoadWithURL:url offset:_tmpSize];
}

/**
 暂停任务
 注意:
 - 如果调用了几次继续
 - 调用几次暂停, 才可以暂停
 - 解决方案: 引入状态
 */
- (void)pauseCurrentTask
{
    if (self.state == HLDownLoadStateDownLoading) {
        self.state = HLDownLoadStatePause;
        [self.dataTask suspend];
    }
}

/**
 继续任务
 - 如果调用了几次暂停, 就要调用几次继续, 才可以继续
 - 解决方案: 引入状态
 */
- (void)resumeCurrentTask
{
    if (self.dataTask && self.state == HLDownLoadStatePause) {
        [self.dataTask resume];
        self.state = HLDownLoadStateDownLoading;
    }
}

/**
 取消当前任务
 */
- (void)cacelCurrentTask
{
    self.state = HLDownLoadStatePause;
    [self.session invalidateAndCancel];
    self.session = nil;
}

/**
 取消任务, 并清理资源
 */
- (void)cacelAndClean
{
    [self cacelCurrentTask];
    [HLFileTool removeFile:self.downLoadingPath];
}

#pragma mark - 协议方法

/**
 第一次接受到相应的时候调用(响应头, 并没有具体的资源内容)
 通过这个方法, 里面, 系统提供的回调代码块, 可以控制, 是继续请求, 还是取消本次请求
 
 @param session 会话
 @param dataTask 任务
 @param response 响应头信息
 @param completionHandler 系统回调代码块, 通过它可以控制是否继续接收数据
 */
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {

    _totalSize = [response.allHeaderFields[@"Content-Length"] longLongValue];
    NSString *contentRangeStr = response.allHeaderFields[@"Content-Range"];
    if (contentRangeStr.length != 0) {
        _totalSize = [[contentRangeStr componentsSeparatedByString:@"/"].lastObject longLongValue];
    }
    
    if (self.downLoadInfo != nil) {
        self.downLoadInfo(_totalSize);
    }
    
    if (_tmpSize == _totalSize) {
        [HLFileTool moveFile:self.downLoadingPath toPath:self.downLoadedPath];
        //  取消本次请求
        completionHandler(NSURLSessionResponseCancel);
        self.state = HLDownLoadStatePauseSuccess;
        return;
    }
    
    //文件下载出现问题 重新下载
    if (_tmpSize > _totalSize) {
        [HLFileTool removeFile:self.downLoadingPath];
        completionHandler(NSURLSessionResponseCancel);
        [self downLoader:response.URL];
        return;
        
    }
    
    self.state = HLDownLoadStateDownLoading;
    self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.downLoadingPath append:YES];
    [self.outputStream open];
    completionHandler(NSURLSessionResponseAllow);
    
}


/**
 当用户确定, 继续接受数据的时候调用
 
 @param session 会话
 @param dataTask 任务
 @param data 接受到的一段数据
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    _tmpSize += data.length;
    self.progress =  1.0 * _tmpSize / _totalSize;
    [self.outputStream write:data.bytes maxLength:data.length];
}


/**
 请求完成时候调用
 请求完成的时候调用( != 请求成功/失败)
 @param session 会话
 @param task 任务
 @param error 错误
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    if (error == nil) {
        // 不一定是成功
        // 数据是肯定可以请求完毕
        // 判断, 本地缓存 == 文件总大小 {filename: filesize: md5:xxx}
        // 如果等于 => 验证, 是否文件完整(file md5 )
        
        [HLFileTool moveFile:self.downLoadingPath toPath:self.downLoadedPath];
        
        self.state = HLDownLoadStatePauseSuccess;
        
    }else {
        if (error.code == -999) {
            self.state = HLDownLoadStatePause;
        }else {
            self.state = HLDownLoadStatePauseFailed;
        }
        
    }
    [self.outputStream close];
    
}

/**** 后台下载 ****/
//- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
//{
//    if (session.configuration.identifier) {
//        // 调用在 -application:handleEventsForBackgroundURLSession: 中保存的 handler
//        [self callCompletionHandlerForSession:session.configuration.identifier];
//    }
//}
//
//- (void)callCompletionHandlerForSession:(NSString *)identifier
//{
//    AppDelegate *delegate =  (AppDelegate *)[UIApplication sharedApplication].delegate;
//    CompletionHandlerType handler = [delegate.completionHandlerDictionary objectForKey: identifier];
//    if (handler) {
//        [delegate.completionHandlerDictionary removeObjectForKey: identifier];
//        NSLog(@"Calling completion handler for session %@", identifier);
//        handler();
//    }
//}


#pragma mark - 私有方法
/**
 根据开始字节, 请求资源
 
 @param url url
 @param offset 开始字节
 */
- (void)downLoadWithURL:(NSURL *)url offset:(long long)offset
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:0];
    // 通过控制range, 控制请求资源字节区间
    [request setValue:[NSString stringWithFormat:@"bytes=%lld-", offset] forHTTPHeaderField:@"Range"];
    // session 分配的task, 默认情况, 挂起状态
    self.dataTask = [self.session dataTaskWithRequest:request];
    
    [self resumeCurrentTask];
}

#pragma mark - 事件/数据传递
- (void)setState:(HLDownLoadState)state
{
    if(_state == state) {
        return;
    }
    _state = state;
    
    if (self.stateChange) {
        self.stateChange(_state);
    }
    
    if (_state == HLDownLoadStatePauseSuccess && self.successBlock) {
        self.successBlock(self.downLoadedPath);
    }
    
    if (_state == HLDownLoadStatePauseFailed && self.faildBlock) {
        self.faildBlock();
    }
}

- (void)setProgress:(float)progress
{
    _progress = progress;
    if (self.progressChange) {
        self.progressChange(_progress);
    }
}


@end
